iT邦幫忙

2022 iThome 鐵人賽

DAY 25
0
Modern Web

Fastify 101系列 第 25

[Fastify] Day25 - Authentication and Authorization

  • 分享至 

  • xImage
  •  

大家好,我是 Yubin

在網頁的應用上,認證 (Authentication) 與授權 (Authorization) 是非常重要的。
本篇文章會介紹兩者的差異以及使用 @fastify/basic-auth 來實作 HTTP 基本認證 (Basic Auth)。


Authentication and Authorization

因為都是 Auth 開頭所以常常有人會搞混,簡單說就是:

Authentication,認證,你是誰。
Authorization,授權,你有沒有權利進行那項操作。

認證 Authentication

網頁應用上,伺服器常常需要知道發送請求的人是誰,用戶端發送 Request 要帶上足夠的資訊證明自己的身份,伺服器收到後,藉著那些資訊去判斷這個 Request 是否真的是那個人,這是認證 (Authentication) 的動作。

如果該 Request 沒有帶上足夠的資訊或認證失敗,伺服器應該回應 401 Unauthorized 的狀態碼。

授權 Authorization

認證動作完成,拿到 Request 發送方的身份後,因為有些動作不是每位使用者都有權限操作,判斷該身份是否有權限進行某個動作,這個是授權 (Authorization)。常見的作法是利用角色 (Role) 來劃分權限。

如果該 Request 代表的身份沒有權限進行當前的操作,伺服器應該回應 403 Forbidden

現實上,很多伺服器的實作不論認證或授權,只要失敗都會回應 403 Forbidden,但我們在開發後端應用程式的時候,提供足夠精確的狀態碼,更能讓存取你的資源的人明白發生了什麼事。

Basic Auth

認證的方式有許多種,其中 HTTP 基本認證 (Basic Access Authentication) 是一個常見且簡單的認證方式。

Basic Auth 利用把使用者帳號、密碼帶在 Request 的 HTTP Header 上,藉由這樣的方式把資訊送給伺服器做認證,伺服器收到資訊後,就可以去檢查帳號密碼有沒有匹配,以此來得知發送這個 Request 的使用者是誰。

帳號密碼的資訊會放在 Header 的 Authorization 欄位。

假設有一個使用者帳號是 user01,密碼是 user01password

會先用冒號 (:) 串起來:

user01:user01password

接著將串起來的字串進行 Basic64 編碼:

dXNlcjAxOnVzZXIwMXBhc3N3b3Jk

編碼完,開頭串上 Basic (注意要用空格做分隔),然後放進 HTTP 的 Authorization 中:

Authorization: Basic dXNlcjAxOnVzZXIwMXBhc3N3b3Jk

發送給伺服器的 Request 就是利用這個方式把 Basic Auth 的資訊帶上。

@fastify/basic-auth

伺服器端也有要有相應的實作,才能處理用戶端帶過來的 Basic Auth 資訊。

因為 Basic Auth 非常單純,所以我們只要把 Request 上面的 Authorization 欄位拿出來,做 Base64 解碼,再用冒號 (:) 進行字串切割,就可以拿到使用者的帳號密碼了。

但這個步驟,雖然簡單但也有點麻煩,
我們可以使用 Fastify 官方維護的 Plugin: @fastify/basic-auth 來幫助我們。


利用 npm 進行安裝:

npm i @fastify/basic-auth

安裝好後透過 server.register() 進行註冊,然後帶入需要使用的 Validatation Handler。

import fastify, {
  FastifyInstance,
  FastifyReply,
  FastifyRequest,
  DoneFuncWithErrOrRes
} from 'fastify'
import fastifyBasicAuth from '@fastify/basic-auth'

function validate(
    username: string,
    password: string,
    request: FastifyRequest,
    reply: FastifyReply,
    done: DoneFuncWithErrOrRes
) {
    // implement your validate mechanism
    if (username === 'user01' && password === 'user01password') {
      done()
    } else {
      done(new Error('username or password incorrect'))
    }
}

server.register(fastifyBasicAuth, { validate })

註冊 plugin 的時候要帶入 Validatation Handler,該函式可以拿到解碼出來的 username, password
可以在這裡實作認證機制。

可以參考官方文件來看更多可用選項。

以上程式完成了 @fastify/basic-auth 的註冊及認證方法定義,但還沒有定義在哪個時機點或範圍進行認證。

Every Request

如果要在每個 Request 進來的時候都進行認證,可以在 FastifyInstance 註冊 onRequest 的 hook:

server.after(() => {
    server.addHook('onRequest', server.basicAuth)
})

這邊把 addHook() 放在 server.after() 的 callback 中,是為了確保執行 addHook() 動作的時候,所有 Plugin 都已經被註冊完成。

這樣一來,我們的程式就具備了 Basic Auth 的能力,而且對於進來的每一個 Request 都會進行認證的動作。


使用 Postman 來打打看:

https://ithelp.ithome.com.tw/upload/images/20221010/20151148wf08bBR3WT.jpg

因為沒有帶上 Basic Auth 的 Header,所以 Fastify App 回應 401 Unauthorized

利用 Postman 加上 Basic Auth 的資訊。 (畫面上只需要輸入 username 跟 password,因為 Postman 會幫我們進行編碼及套上正確的格式)

https://ithelp.ithome.com.tw/upload/images/20221010/2015114870tIyRRl0P.jpg

輸入正確的資訊,通過 Validatation Handler 的驗證,就可以存取到相應的資源。


如果帶入的 Basic Auth 資訊錯誤

https://ithelp.ithome.com.tw/upload/images/20221010/201511483I1MHXE28n.jpg

除了回傳 401 表示驗證不通過外,可以看到 message 欄位是我們從 Validatation Handler 那邊拋出的錯誤訊息,可以讓發送端收到未通過驗證的理由。

Specific Route

如果不想要每個 Request 都進行認證的工作,可以像這樣定義在 route scope 上:

server.after(() => {
    server.get('/', { onRequest: server.basicAuth }, (request, reply) => {
      request
      return reply.status(200).send({ message: 'Hello user01' })
    })
})

server.get('/hello', (request, reply) => {
    return reply.status(200).send({ message: 'Hello World' })
})

上述範例程式,只有 GET / 會經過 Basic Auth 的認証,GET /hello 不需經過認證。


本篇介紹了基本的認證與授權,並以 @fastify/basic-auth 這個官方 Plugin 實作了 Fastify App 的 Basic Auth 認證機制。

但使用 Basic Auth 來進行驗證有滿多事情要注意的,因為帳號密碼只有透過 Base64 編碼,因為是編碼不是加密,所以如果沒有搭配 https,等於是把帳號密碼公開在網路傳輸上。

就算有使用 https 來做傳輸上的加密,帳號密碼的管理也要謹慎。

Basic Auth 比較適合在足夠單純的網路環境或使用情境中,要做驗證或授權,更靠譜且更多人使用的會是 Token 或 OAuth2 等技術來進行。

以上完整範例程式,可以參考 GitHub


上一篇
[Fastify] Day24 - Upload File to Object Storage (MinIO)
下一篇
[Fastify] Day26 - 前後端整合 React and Fastify-Static
系列文
Fastify 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言